package br.unifor.mia.graph.setorcensitario;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.jgrapht.alg.BellmanFordShortestPath;
import org.jgrapht.alg.ConnectivityInspector;
import org.jgrapht.alg.NeighborIndex;
import org.jgrapht.graph.SimpleGraph;

public class Metrics {

	private SimpleGraph<String, String> graph;

	private PathMatrix matrix;

	private List<Link> allLinks;

	private PathMatrix createPathMatrix(SimpleGraph<String, String> graph) {

		List<String> nodesList = new ArrayList<String>(graph.vertexSet());

		int nodeListSize = nodesList.size();

		PathMatrix matrix = new PathMatrix(nodesList);

		for (int i = 0; i < nodeListSize; i++) {

			String startNode = nodesList.remove(0);

			if (!graph.containsVertex(startNode)) {
				continue;
			}

			String endNode = null;
			double maiorCusto = 0;

			BellmanFordShortestPath<String, String> shortestPath = new BellmanFordShortestPath<String, String>(
					graph, startNode);

			for (String nextNode : nodesList) {
				if (!graph.containsVertex(nextNode)) {
					continue;
				}
				double custo = shortestPath.getCost(nextNode);
				if (custo != Double.POSITIVE_INFINITY && custo > maiorCusto) {
					maiorCusto = custo;
					endNode = nextNode;
				}
			}

			// Insere na PathMatrix
			if (endNode != null) {
				matrix.add(startNode, endNode, maiorCusto);
			}
		}

		return matrix;
	}

	private int getGiantComponentSize(List<Link> allLinks) {

		SimpleGraph<String, String> graph = new SimpleGraph<String, String>(
				String.class);

		for (Link link : allLinks) {
			graph.addVertex(link.getUser().getName());
			graph.addVertex(link.getSector().getName());
			graph.addEdge(link.getUser().getName(), link.getSector().getName(),
					link.getName());
		}

		ConnectivityInspector<String, String> strong = new ConnectivityInspector<String, String>(
				graph);

		Set<String> giantComponent = null;

		for (Set<String> component : strong.connectedSets()) {

			if (giantComponent == null) {
				giantComponent = component;
			} else {
				if (component.size() > giantComponent.size()) {
					giantComponent = component;
				}
			}
		}
		return giantComponent.size();
	}

	public Metrics(List<Link> allLinks, boolean createMatrix) {

		this.allLinks = allLinks;

		SimpleGraph<String, String> graph = new SimpleGraph<String, String>(
				String.class);

		for (Link link : allLinks) {
			graph.addVertex(link.getUser().getName());
			graph.addVertex(link.getSector().getName());
			graph.addEdge(link.getUser().getName(), link.getSector().getName(),
					link.getName());
		}

		this.graph = graph;

		if (createMatrix) {
			matrix = createPathMatrix(this.graph);
		}
	}

	public Metrics(List<Link> allLinks) {
		this(allLinks, true);
	}

	public double getDiameter() {
		return matrix.getDiameter();
	}

	public double getAveragePath() {
		return matrix.getAveragePath();
	}

	public double getCentrality() {
		return matrix.getCentrality();
	}

	public int getNumberOfComponents() {
		ConnectivityInspector<String, String> strong = new ConnectivityInspector<String, String>(
				graph);
		return strong.connectedSets().size();
	}

	public Set<String> getGiantComponent() {

		ConnectivityInspector<String, String> strong = new ConnectivityInspector<String, String>(
				graph);

		Set<String> giantComponent = null;

		for (Set<String> component : strong.connectedSets()) {

			if (giantComponent == null) {
				giantComponent = component;
			} else {
				if (component.size() > giantComponent.size()) {
					giantComponent = component;
				}
			}
		}

		return giantComponent;
	}

	public void printGiantComponentEvolution() {

		int tamanho = allLinks.size();

		List<Link> listaEvolutiva = new ArrayList<Link>();

		System.out.println("#########Giant-Component Evolution###################");
		for (int i = 0; i < tamanho; i++) {
			listaEvolutiva.add(allLinks.remove(0));
			System.out.println(getGiantComponentSize(listaEvolutiva));
		}
		System.out.println("#####################################################");
		allLinks = listaEvolutiva;
	}

	public double getDensity() {

		int nodes = graph.vertexSet().size();
		int links = graph.edgeSet().size();

		System.out.println("# Nodes: " + nodes);
		System.out.println("# Links: " + links);

		long maxNodes = (nodes * (nodes - 1)) / 2;

		double density = links;

		density = density / maxNodes;

		return density;
	}

	public double getNodeClusterCoefficient(String node) {

		NeighborIndex<String, String> neighborIndex = new NeighborIndex<String, String>(
				graph);

		List<String> neighbors = new ArrayList<String>(
				neighborIndex.neighborsOf(node));

		int neighborsSize = neighbors.size();

		Set<String> sharedLinks = new HashSet<String>();

		for (int i = 0; i < neighborsSize; i++) {
			String firstNeighbor = neighbors.remove(0);

			for (String nextNeighbor : neighbors) {
				if (graph.containsEdge(firstNeighbor, nextNeighbor)) {
					sharedLinks.add(graph.getEdge(firstNeighbor, nextNeighbor));
				}
			}
		}

		int degree = graph.degreeOf(node);

		if (degree == 0 || degree == 1) {
			return 0;
		} else {
			return 2 * sharedLinks.size() / (degree * (degree - 1));
		}
	}

	public double getGraphClusterCoefficient() {

		double result = 0;

		for (String node : graph.vertexSet()) {
			result += getNodeClusterCoefficient(node);
		}

		double cc = result / graph.vertexSet().size();

		return cc;
	}

	public double getLinkEfficiency() {
		double result = 1 - (getAveragePath() / graph.edgeSet().size());
		return result;
	}

	/**
	 * @return the nodes
	 */
	public Set<String> getNodes() {
		return graph.vertexSet();
	}

	public List<String> getHubs() {

		List<String> hubs = new ArrayList<String>();

		int maxDegree = 0;
		for (String node : getNodes()) {
			if (graph.degreeOf(node) > maxDegree) {
				maxDegree = graph.degreeOf(node);
				hubs.clear();
				hubs.add(node);
			} else {
				if (graph.degreeOf(node) == maxDegree) {
					hubs.add(node);
				}
			}
		}
		return hubs;
	}

	public void printEntropy() {

		List<String> hubs = getHubs();

		int nodesSide = getNodes().size();

		// Gera sequencia de graus
		double[] sequence = new double[graph.degreeOf(hubs.get(0)) + 1];
		for (String node : getNodes()) {
			sequence[graph.degreeOf(node)]++;
		}

		for (int i = 0; i < sequence.length; i++) {
			if (sequence[i] != 0) {
				sequence[i] = sequence[i] / nodesSide;
			}
		}

		double entropy = 0;

		for (int i = 0; i < sequence.length; i++) {
			if (sequence[i] != 0) {
				entropy += sequence[i]
						* (Math.log10(sequence[i]) / Math.log10(2));
			}
		}

		System.out.println("Entropy: " + -1 * entropy);
	}

	public void printDegreeSequence() {

		List<String> hubs = getHubs();

		// Imprime Hubs
		System.out.println("###########################");
		for (String h : hubs) {
			System.out.println("Hub: " + h + " Grau: " + graph.degreeOf(h));
		}
		System.out.println("###########################");

		// Gera sequencia de graus
		int[] sequenciaDeGraus = new int[graph.degreeOf(hubs.get(0)) + 1];
		for (String node : getNodes()) {
			sequenciaDeGraus[graph.degreeOf(node)]++;
		}

		// Imprime Sequencia de graus
		for (int i = 0; i < sequenciaDeGraus.length; i++) {
			if (sequenciaDeGraus[i] != 0) {
				System.out.println(" [" + i + " = " + sequenciaDeGraus[i]
						+ "] ");
			}
		}
	}

	public void printMetrics() {

		this.printDegreeSequence();
		this.printEntropy();

		this.printGiantComponentEvolution();

		System.out.println("Path Average: " + this.getAveragePath());
		System.out.println("Centrality: " + this.getCentrality());
		System.out.println("Diameter: " + this.getDiameter());
		System.out.println("Density: " + this.getDensity());
		System.out.println("Link Efficiency: " + this.getLinkEfficiency());
		System.out.println("Graph Cluster Coefficient: " + this.getGraphClusterCoefficient());
		System.out.println("Number of Components: " + this.getNumberOfComponents());
		System.out.println("Giant-Component size: " + this.getGiantComponent().size());
	}
}
